#include "system/events.h"
#include "devices/keyboard_private.h"
#include "devices/mouse_private.h"
#include "graphics/render_private.h"

#include <stdio.h>

// Conditions pour que l'évènement soit validé
bool _esdlTriggerEventCondition(const EsdlEvent* event) {
    switch (event->eventType) {
        case ON_MOUSE_UP:
        case ON_MOUSE_DOWN:
        case ON_FOCUS:
        case ON_MOUSE_MOVE:
            // Il faut que le curseur soit sur le sprite lié à l'évènement
            return window.mouse.x > esdlgetAbsoluteXPosition(event->sprite)
                    && window.mouse.x < esdlgetAbsoluteXPosition(event->sprite) + event->sprite->visiblePart.w
                    && window.mouse.y > esdlGetAbsoluteYPosition(event->sprite)
                    && window.mouse.y < esdlGetAbsoluteYPosition(event->sprite) + event->sprite->visiblePart.h
                    ;
            break;
        case ON_MOUSE_ENTER:
        case ON_MOUSE_LEAVE:
            return event->sprite->over;
            break;
        case ON_KEY_UP:
        case ON_KEY_DOWN:
        case ON_BLUR:
            return event->sprite->focus;
            break;
        default:
            break;
    }
    return false;
}

bool _esdlTriggerEvent(EsdlEvent* event) {
    if (event->sprite == NULL) {
        return false;
    }

    // Si le sprite n'est pas affiché, on ne propage pas l'évènement
    if (!event->sprite->show) {
        return false;
    }

    if (!event->propagate || !_esdlTriggerEventCondition(event)) {
        return false;
    }

    EsdlSprite * sprite = event->sprite;

    // Lauch Capturing Callbacks
    //if(event->propagate)	printf("capturing (event %d)\n", event->eventType);
    EsdlLinkedList clisteners = event->sprite->capturingListeners;
    while (event->propagate && clisteners != NULL) {
        if (((EsdlListener*) clisteners->data)->eventType == event->eventType) {
            ((EsdlListener*) clisteners->data)->callback(event);
        }
        clisteners = clisteners->next;
    }

    // propagate		
    EsdlLinkedList l = event->sprite->children;
    if (l != NULL) {
        // On part du dernier enfant (celui avec le zindex le plus élevé)
        while (l->next != NULL) {// <=== Commenter !
            l = l->next;
        }
        
        // On propage l'évènement
        while (l != NULL && event->propagate) {
            event->sprite = (EsdlSprite*) l->data;
            if (_esdlTriggerEvent(event)) {
                l = NULL;
            } else {
                l = l->previous;
            }
        }
    }

    // Lauch Bubbling Callbacks
    event->sprite = sprite;
    //if(event->propagate)	printf("bubbling (event %d)\n", event->eventType);
    EsdlLinkedList blisteners = event->sprite->bubblingListeners;
    while (event->propagate && blisteners != NULL) {
        if (((EsdlListener*) blisteners->data)->eventType == event->eventType)
            ((EsdlListener*) blisteners->data)->callback(event);
        blisteners = blisteners->next;
    }

    return true;
}

// Déclanche les évènement ON_MOUSE_ENTER et ON_MOUSE_LEAVE
void _esdlSetOver(EsdlEvent* event, EsdlSprite* sprite, bool over) {
    event->eventType = (over ? ON_MOUSE_ENTER : ON_MOUSE_LEAVE);
    event->sprite = esdlGetScreen();

    if (event->eventType == ON_MOUSE_LEAVE) {
        _esdlTriggerEvent(event);
    }

    while (sprite != NULL) {
        sprite->over = over;
        sprite = sprite->parent;
    }

    if (event->eventType == ON_MOUSE_ENTER) {
        _esdlTriggerEvent(event);
    }
}

bool _esdlGetCurrentSpriteOver(EsdlEvent* event, EsdlSprite** sprite) {
    if (event->sprite == NULL || !event->sprite->over) return false;
    *sprite = event->sprite;

    EsdlLinkedList l = event->sprite->children;
    if (l != NULL) {
        while (l->next != NULL)
            l = l->next;

        while (l != NULL) {
            event->sprite = (EsdlSprite *) l->data;
            if (_esdlGetCurrentSpriteOver(event, sprite)) {
                l = NULL;
            } else {
                l = l->previous;
            }
        }
    }

    return true;
}

bool _esdlCheckOver(EsdlEvent* event, EsdlSprite** sprite) {
    if (event->sprite == NULL) return false;
    if (!_esdlTriggerEventCondition(event)) return false;
    if (event->sprite->show == false) return false;

    *sprite = event->sprite;

    EsdlLinkedList l = event->sprite->children;
    if (l != NULL) {
        while (l->next != NULL)
            l = l->next;

        while (l != NULL) {
            event->sprite = (EsdlSprite *) l->data;
            if (_esdlCheckOver(event, sprite)) {
                l = NULL;
            } else {
                l = l->previous;
            }
        }
    }

    return true;
}

void esdlEventLoop(EsdlRenderCallback callback) {
    SDL_Event event;

    // Frame time information
    esdlGetRender()->updateTime = SDL_GetTicks();

    // Loop
    window.eventLoop = true;
    while (window.eventLoop) {
        // Get a new event
        while (SDL_PollEvent(&event)) {

            /* Mouse & Keyboard Update */

            // Update the mouse position in Window object
            esdlGetMouse()->x = event.button.x;
            esdlGetMouse()->y = event.button.y;

            switch (event.type) {
                    // Close event
                case SDL_QUIT:
                    window.eventLoop = 0;
                    break;
                    // Update the mouse state in Window object
                case SDL_MOUSEBUTTONDOWN:
                    _esdlMouseSetButtonState(esdlGetMouse(), event.button.button, true);
                    break;
                case SDL_MOUSEBUTTONUP:
                    _esdlMouseSetButtonState(esdlGetMouse(), event.button.button, false);
                    break;
                case SDL_KEYDOWN:
                    _esdlKeyboardSet(esdlGetKeyboard(), event.key.keysym.sym, true);
                    break;
                case SDL_KEYUP:
                    _esdlKeyboardSet(esdlGetKeyboard(), event.key.keysym.sym, false);
                    break;
            }


            /* Trigger events */

            // init even
            EsdlSprite * current_sprite_over = NULL;
            EsdlSprite * new_sprite_over = NULL;
            EsdlEvent e;
            e.sprite = esdlGetScreen();
            e.event = &event;
            e.propagate = true;

            // trigger event
            switch (event.type) {
                case SDL_MOUSEBUTTONUP:
                    e.eventType = ON_MOUSE_UP;
                    _esdlTriggerEvent(&e);
                    break;
                case SDL_MOUSEBUTTONDOWN:
                    e.eventType = ON_MOUSE_DOWN;
                    _esdlTriggerEvent(&e);
                    e.eventType = ON_BLUR;              // On enlève le focus avant de le réattribuer !
                    _esdlTriggerEvent(&e);
                    e.eventType = ON_FOCUS;
                    _esdlTriggerEvent(&e);
                    break;
                case SDL_MOUSEMOTION:
                    e.eventType = ON_MOUSE_MOVE;
                    _esdlTriggerEvent(&e);
                    _esdlGetCurrentSpriteOver(&e, &current_sprite_over);
                    e.sprite = esdlGetScreen();
                    _esdlCheckOver(&e, &new_sprite_over);
                    if (current_sprite_over != new_sprite_over) {
                        _esdlSetOver(&e, current_sprite_over, false);
                        _esdlSetOver(&e, new_sprite_over, true);
                    }
                    break;
                case SDL_KEYDOWN:
                    e.eventType = ON_KEY_DOWN;
                    _esdlTriggerEvent(&e);
                    break;
                case SDL_KEYUP:
                    e.eventType = ON_KEY_UP;
                    _esdlTriggerEvent(&e);
                    break;
                default:
                    break;
            }

        }

        // renderer
        _esdlRenderScreen(callback);
    }
}

void esdlStopEventLoop() {
    window.eventLoop = false;
}

EsdlKey esdlGetKey(const EsdlEvent* event) {
    return (event->event ? event->event->key.keysym.sym : KEY_UNKNOWN);
}

EsdlMouseButton esdlGetMouseButton(const EsdlEvent* event) {
    return (event->event ? event->event->button.button : KEY_UNKNOWN);
}

EsdlSprite* esdlGetSpriteEvent(const EsdlEvent* event) {
    return event->sprite;
}

void esdlStopPropagateEvent(EsdlEvent* event) {
    event->propagate = false;
}


